package MaquinaVirtual;

import java.util.ArrayList;
import java.util.Stack;
import java.util.StringTokenizer;
import javax.swing.JOptionPane;

//import AnalizadorLexico.AnalizadorLexico;

public class MaquinaPila {


	private float memoria[];// Memoria principal con celdas direccionables con datos.
	private Stack<Float> pila;//Pila de datos con celdas direccionables con datos.
	private ArrayList<String> programa;
	
	private int cProg; // contador de programa para la instruccion actual.
	private int cPila; // cima de la pila
	private boolean p; // bit de parada para detener ejecucion. 
	private boolean s1; // bit para swap1. Valor: true si tiene que invertir la suma y resta. false en caso contrario.
	private boolean s2; // bit para swap2. Valor: true si tiene que invertir multiplicación y división. false en caso contrario.
	private boolean divisionCero; // bit para division por cero. Valor: true si se intenta dividir un numero entre 0. False en caso contrario
	private boolean errorLecturaTipoIncorrecto; // bit para error de lectura. Valor: true cuando se produce un error en la lectura de tipo. False en caso contrario.
	
	private String mensajeError;
	private String resultado;
	
	
	/*public static void main (String args[]){

		//String s = "apila(0) desapila_dir(0) apila(0) desapila_dir(1) read(float) desapila_dir(1) apila_dir(1) casting(int) desapila_dir(0) | stop";
		ArrayList<String> s=new ArrayList<String>();
		s.add("apila(0)");
		s.add("desapila_dir(0)");
		s.add("apila(0)");
		s.add("desapila_dir(1)");
		s.add("read(float)");
		//s.add("apila(5)");//Prueba
		s.add("desapila_dir(1)");
		s.add("apila_dir(1)");
		s.add("casting(int)");
		s.add("desapila_dir(0)");
		s.add("stop");
		MaquinaPila mp = new MaquinaPila(s, false);
	}
	*/
	
	/*
	 * Creamos una memoria con 1000 posiciones.
	 * Creamos la pila.
	 * Inicializamos la cola de la pila, el contador de programa y (cPila y cProg).
	 * Procesamos el codigo de entrada (String) mediante StringTokenizer quitando "(", ")" y " ".
	 * Con un while procesamos el codigo separando por instrucciones y viendo que función hay que usar.
	 * Se termina de procesar cuando p = 1 o se ha producido algun error.
	 * 
	 */
	public MaquinaPila(ArrayList<String> codigo){
		
		mensajeError = resultado = "";
		
		memoria = new float[1000]; // creamos una memoria de 1000 posiciones
		pila = new Stack<Float>(); // creamos la pila
		programa=codigo;
		
		cPila = -1;
		cProg = 0;
		p = false;
		
		ejecutar();
		//imprimirMemoria();
		
	}
	
	public void ejecutar() {
		//Covertimos en string de entrada con el codigo en tokens separados por espacios.
		StringTokenizer st;
		String inst;
		
		//int i=0;
		while(cProg<programa.size() && !p && !divisionCero && !errorLecturaTipoIncorrecto){
			inst=programa.get(cProg);
			//st= new StringTokenizer(inst,"("+")"+" ");
			//inst = st.nextToken();
			String[] aux=eliminaCaracteres(inst);
			switch (aux[0]){
				case "apila": apila(Float.parseFloat(aux[1])); break;
				case "apila_dir": apila_dir(Integer.parseInt(aux[1])); break;
				case "desapila_dir": desapila_dir(Integer.parseInt(aux[1])); break;
				case "suma": suma(aux[1]); break;
				case "resta": resta(aux[1]); break;
				case "multiplica": multiplica(aux[1]); break;
				case "divide": divide(aux[1]); break;
				case "modulo": modulo(); break;
				case "or": or(); break;
				case "and": and(); break;
				case "not": not(); break;
				case "mayor": mayor(); break;
				case "menor": menor(); break;
				case "mayorIgual": mayorIgual(); break;
				case "menorIgual": menorIgual(); break;
				case "igual":  igual(); break;
				case "distinto": distinto(); break;
				case "negativo": negativo(); break;
				case "swap1": swap1(); break;
				case "swap2": swap2(); break;
				case "read": read(aux[1]); break;
				case "write": write(aux[1]); break;
				case "desplazarIzq": desplazarIzq(); break;
				case "desplazarDer": desplazarDer(); break;
				case "casting": casting(aux[1]); break;
				case "stop": stop(); break;
				case "apila_ind": apilaInd(); break;
				case "desapila_ind" : desapilaInd(); break;
				case "copia" : copia(); break;
				case "ir_v" : irV(Integer.parseInt(aux[1])); break;
				case "ir_f" : irF(Integer.parseInt(aux[1])); break;
				case "ir_a" : irA(Integer.parseInt(aux[1])); break;
                                case "ir_ind" : irInd(); break;
                                case "mueve" : mueve(Integer.parseInt(aux[1])); break;
                                case "desapila" : desapila(); break;
			}
			//i++;
		}

		if (divisionCero) mensajeError = "Error division por cero. Instruccion de pila num " + cProg + ".\n";
		if (errorLecturaTipoIncorrecto) mensajeError = "Error de lectura de tipo. Instruccion de pila num " + cProg + ".\n";
	}
	
	private void imprimirMemoria()
	{
		for(int i=0;i<memoria.length;i++)
			System.out.println(memoria[i]);
	}
	
	private String[] eliminaCaracteres(String s)
	{
		StringTokenizer st= new StringTokenizer(s,"("+")"+" ");
                int tamArray=2;
		String[] aux=new String[tamArray];
		aux[0]=st.nextToken();
                int i=1;
		while(st.hasMoreTokens()&&i<tamArray)
                {
                    aux[i]=st.nextToken();
                    i++;
                }
		return aux;
	}


	// Apila el valor número en la pila
	public void apila(float numero) {
			cPila++;
			pila.push(numero);
			cProg++;
	}
	
	// Apila el valor que hay en una dirección de memoria que le entra.
	public void apila_dir(int dir) {
		cPila++;
		pila.push(memoria[dir]);
		cProg++;
	}
	
	// Guarda en una dirección de memoria dada el valor que haya en la cima de la pila.
	public void desapila_dir(int dir) {
		
		memoria[dir] = pila.pop();
		cPila--;
		cProg++;
	}
        
        // Guarda en una dirección de memoria dada el valor que haya en la cima de la pila.
	public void desapila() {
		
		pila.pop();
		cPila--;
		cProg++;
	}
	/* Miramos si se produce swap1 para ver si hay que sumar o restar.
	 * Guarda en la cima de la pila la suma o resta de los valores que están la cima de pila y cima de pila -1.
	 *  */
	public void suma(String tipo) {
		
                float v1 = pila.pop();
		float v2 = pila.pop();
                
                if (tipo == null) suma(v1, v2);
                else {
                    if (!s1) // no hay swap1
                            pila.push(v2+v1); 
                    else {
                        if (tipo.equals("natural") && (v2-v1 < 0))
                            pila.push((float) 0); 
                        else pila.push(v2-v1); // hay swap1
                    }
                }
		
		cPila--;
		cProg++;
	}
               
        /* No les afecta el swap para no interferir en el funcionamiento de los accesos
         * a los designadores  */
        public void suma(float v1, float v2) {
            pila.push(v2+v1); 
	}        
                
	
	/* Miramos si se produce swap1 para ver si hay que sumar o restar.
	 * Guarda en la cima de la pila la suma o resta de los valores que están la cima de pila y cima de pila -1.
	 *  */
	public void resta(String tipo) {
		
		float v1 = pila.pop();
		float v2 = pila.pop();
		
                if (tipo == null) resta(v1, v2);
                else {
                    if (!s1) { // no hay swap1
                            if (tipo.equals("natural") && (v2-v1 < 0))
                                    pila.push((float) 0); 
                            else pila.push(v2-v1); 
                    }
                    else pila.push(v2+v1); // hay swap1
                }	
		cPila--;
		cProg++;
	}
        
        /* Miramos si se produce swap1 para ver si hay que sumar o restar.
	 * Guarda en la cima de la pila la suma o resta de los valores que están la cima de pila y cima de pila -1.
	 *  */
	public void resta(float v1, float v2) {
		
		pila.push(v2-v1); 
	}
        
	
	/* Miramos si se produce swap2 para ver si hay que multiplicar o dividir.
	 * Guarda en la cima de la pila el producto o la division de los valores que están la cima de pila y cima de pila -1.
	 *  */
	public void multiplica(String tipo) {
		
		float v1 = pila.pop();
		float v2 = pila.pop();
		
		if (tipo == null ) multiplica (v1, v2);
                else {
                    if (tipo.equals("natural") || tipo.equals("integer")){
                            int int1 = (int) v1;
                            int int2 = (int) v2;

                            if (!s2) pila.push(v2*v1);  // no hay swap2
                            else { // hay swap2
                                    if (int1!=0) pila.push((float) (int2/int1));	
                                    else divisionCero = true;
                            }		
                    }
                    else {
                            if (!s2) pila.push(v2*v1);  // no hay swap2
                            else { // hay swap2
                                if (v1!=0) pila.push(v2/v1);	
                                else divisionCero = true;
                            }
                    }
                }			
		cPila--;
		cProg++;
	}
        
        /* No les afecta el swap para no interferir en el funcionamiento de los accesos
         * a los designadores  */
        public void multiplica(float v1, float v2) {
		pila.push(v2*v1); 	
	}        
             
	
	/* Miramos si se produce swap2 para ver si hay que multiplicar o dividir.
	 * Guarda en la cima de la pila el producto o la division de los valores que están la cima de pila y cima de pila -1.
	 *  */
	public void divide(String tipo) {
		
		float v1 = pila.pop();
		float v2 = pila.pop();
		
		if (tipo.equals("natural") || tipo.equals("integer")){
			int int1 = (int) v1;
			int int2 = (int) v2;
			
			if (!s2) { // no hay swap2
				if (int1!=0) pila.push((float) (int2/int1));  // division entera
				else divisionCero = true;
			} else pila.push(v2*v1); // hay swap2
			
		}
		else {
			if (!s2) { // no hay swap2
				if (v1!=0) pila.push(v2/v1);  // division real
				else divisionCero = true;
			} else pila.push(v2*v1); // hay swap2
		}
			
		cPila--;
		cProg++;
	}
	
	// Guarda en la cima de la pila el modulo de los valores que están la cima de pila y cima de pila -1.
	public void modulo() {
			
		float v1 = pila.pop();
		float v2 = pila.pop();
			
		pila.push(v2%v1);
				
		cPila--;
		cProg++;
	}
	
	// Guarda en la cima de la pila la or de los valores que están la cima de pila y cima de pila -1.
	public void or() {
				
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v1 == 1 || v2 == 1)
			pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}

	// Guarda en la cima de la pila la and de los valores que están la cima de pila y cima de pila -1.
	public void and() {
				
		float v1 = pila.pop();
		float v2 = pila.pop();
			
		if (v1 == 1 && v2 == 1)
			pila.push((float) 1);
		else pila.push((float) 0);
						
		cPila--;
		cProg++;
	}
	
	// Guarda en la cima de la pila la negacion del valor de la cima de pila.
	public void not() {
				
		float v = pila.pop();
			
		if (v == 1)
			pila.push((float) 0);
		else if (v == 0)
			pila.push((float) 1);
		
		cPila--;
		cProg++;
	}
	
	/*  Devuelve un 1 si el valor de la cima de la pila -1 es mayor que el valor de la cima.
	    Un cero en caso contrario. */
	public void mayor() {
		
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v2 > v1) pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}	

	/*  Devuelve un 1 si el valor de la cima de la pila -1 es menor que el valor de la cima.
        Un cero en caso contrario. */
	public void menor() {
	
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v2 < v1) pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}		

	/*  Devuelve un 1 si el valor de la cima de la pila -1 es mayor que el valor de la cima.
    Un cero en caso contrario. */
	public void mayorIgual() {
		
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v2 >= v1) pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}	
	
	/*  Devuelve un 1 si el valor de la cima de la pila -1 es menor que el valor de la cima.
	    Un cero en caso contrario. */
	public void menorIgual() {
	
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v2 <= v1) pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}
	
	/*  Devuelve un 1 si el valor de la cima de la pila -1 es igual que el valor de la cima.
    Un cero en caso contrario. */
	public void igual() {
		
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v2 == v1) pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}	
	
	/*  Devuelve un 1 si el valor de la cima de la pila -1 es distinto que el valor de la cima.
	    Un cero en caso contrario. */
	public void distinto() {
	
		float v1 = pila.pop();
		float v2 = pila.pop();
				
		if (v2 != v1) pila.push((float) 1);
		else pila.push((float) 0);
					
		cPila--;
		cProg++;
	}	
	
	// Deja en la cima de la pila, el valor que haya en la cima  de la pila cambiado de signo. 
	public void negativo() {
	
		float v = pila.pop();
				
		pila.push(v * (-1));
					
		cPila--;
		cProg++;
	}
	
	// Intercambia el + por - y viceversa cambiando a 1 el bit del registro s1.
	public void swap1() {
		s1 = !s1;
		cProg++;
	}
	
	// Intercambia el * por / y viceversa cambiando a 1 el bit del registro s2.
	public void swap2() {
		s2 = !s2;
		cProg++;
	}
	
	/* Apila un valor introducido por el usuario.
	 * Le entra el tipo para hacer la distinción a la hora de apilarlo.
	 */
        
	public void read(String tipo){
		String aux;
		cPila = cPila + 1;
		switch(tipo){
			case "boolean" : aux = JOptionPane.showInputDialog("Escribe un booleano");
							 if (aux.equals("true")) apila(1);
							 else if (aux.equals("false")) apila(0);
							 else errorLecturaTipoIncorrecto = true;
							 break;
							 
			case "natural" : try{ 
					        int auxInt = Integer.parseInt(JOptionPane.showInputDialog("Escribe un natural"));
					        if (auxInt < 0) errorLecturaTipoIncorrecto = true;
					        else apila(auxInt); 
		        		} catch(Exception e){ errorLecturaTipoIncorrecto = true; } break;
			
			case "integer" :  try{ apila(Integer.parseInt(JOptionPane.showInputDialog("Escribe un integer"))); }
							  catch(Exception e){ errorLecturaTipoIncorrecto = true; } break;				 
							
			case "float" : try{ 
		        aux = JOptionPane.showInputDialog("Escribe un float");

		        if (comprobarFloat(aux)) apila(Float.parseFloat(aux)); 
		        else  errorLecturaTipoIncorrecto = true; 

		       }
		         catch(Exception e){ errorLecturaTipoIncorrecto = true; } break;
			
			case "character" : aux = JOptionPane.showInputDialog("Escribe un character");
	          if (aux.length()==1) {
	           if (Character.isDigit(aux.charAt(0)) || Character.isLowerCase(aux.charAt(0)) || Character.isUpperCase(aux.charAt(0)))
	             apila(aux.charAt(0));
	           else errorLecturaTipoIncorrecto = true; 
	          }
	          else errorLecturaTipoIncorrecto = true; 
	          break;
		}	    
	  			
	   //cProg++;
	}
	
	/* Muestra el valor de lo que haya en la cima de la pila. 
	 *  Le entra hacer la distincion de lo que tiene que mostrar */
	public void write(String tipo){
		
                resultado = "";  /**FIXME quitar esto*/
            
		float elem = pila.pop();
		switch(tipo){
			case "boolean" : if (elem==0) resultado += "false";
							 else resultado += "true";
							 break;
			case "natural" : resultado += (int)elem; break;
			case "integer" : resultado +=  (int)elem; break;
			case "float" : resultado += elem; break;
			case "character" : resultado += (char)elem; break;
		}

		cPila = cPila - 1;
		cProg++;
                
                System.out.println(resultado);
	} 
	
	/*Aplica el casting al valor que exista en la cima de la pila*/
	public void casting(String tipo)
	{
		//Obtenemos el valor de la cima de la pila
		float elem = pila.pop();
		switch(tipo){
			case "nat" : pila.push((float)((int)elem)); break;
			case "int" : pila.push((float)((int)elem)); break;
			case "float" : pila.push((float)elem); break;
			case "char" :pila.push((float)((char)elem)); break;
		}

		
		cProg++; 
	}
	
	/* Deja en la cima de la pila el resultado de desplazar a la izquierda el valor de la cima de 
	 * pila -1 tantas veces como indique el valor de la cima de la pila
	   Se desplaza multiplicando * 2. (Tambien se podria con >>) */
	public void desplazarIzq(){
		float v1 = pila.pop();
		float v2 = pila.pop();
		
		pila.push((float) (v2*(Math.pow(2,v1))));

		cPila--;
		cProg++;
	}
	
	/* Deja en la cima de la pila el resultado de desplazar a la derecha el valor de la cima de 
	 * pila -1 tantas veces como indique el valor de la cima de la pila.
	 * Se desplaza dividiendo / 2. (También se podria con <<) */
	public void desplazarDer(){
		float v1 = pila.pop();
		float v2 = pila.pop();
		
		pila.push((float) (v2/(Math.pow(2,v1))));

		cPila--;
		cProg++;
	}

	// Pone a 1 el bit de parada.
	public void stop(){
		p = true;
	}
        
        public void irInd()
        {
            //PC←Pila[ST]
            //ST←ST-1
            cProg=Math.round(pila.peek());
            cPila--;
        }
	
	public void apilaInd()
	{
		//float top=memoria[cPila];
                float top=memoria[Math.round(pila.peek())];
		pila.pop();
		pila.push(top);
		cProg++;
	}
	
	public void desapilaInd()
	{
		float v=pila.pop();
		int d=Math.round(pila.pop());
		memoria[d]=v;
		cPila-=2;
		cProg++;
	}
	
	public void copia()
	{
		pila.push(pila.peek());
		cPila++;
		cProg++;
	}
	
	public void irV(int s)
	{
		if(pila.peek()!=0)
			cProg=s;
		else
			cProg++;
		cPila--;
	}
	
	public void irF(int i)
	{
		if(pila.peek()==0)
			cProg=i;
		else
			cProg++;
		cPila--;
	}
	
	public void irA(int i)
	{
		cProg=i;
	}
        
        public void mueve(int tam)
        {
            int dir1=new Float(pila.pop()).intValue();
            int dir2=new Float(pila.pop()).intValue();
            cPila-=2;
            int i=0;
            while(i<tam) {
                memoria[dir2+i]=memoria[dir1+i];
                i++;
            }
            cProg++;
        }

	public float[] getMemoria() {
		return memoria;
	}

	public void setMemoria(float[] memoria) {
		this.memoria = memoria;
	}

	public Stack<Float> getPila() {
		return pila;
	}

	public void setPila(Stack<Float> pila) {
		this.pila = pila;
	}

	public int getcProg() {
		return cProg;
	}

	public void setcProg(int cProg) {
		this.cProg = cProg;
	}

	public int getcPila() {
		return cPila;
	}

	public void setcPila(int cPila) {
		this.cPila = cPila;
	}

	public boolean isP() {
		return p;
	}

	public void setP(boolean p) {
		this.p = p;
	}

	public boolean isS1() {
		return s1;
	}

	public void setS1(boolean s1) {
		this.s1 = s1;
	}

	public boolean isS2() {
		return s2;
	}

	public void setS2(boolean s2) {
		this.s2 = s2;
	}

	public boolean isDivisionCero() {
		return divisionCero;
	}

	public void setDivisionCero(boolean divisionCero) {
		this.divisionCero = divisionCero;
	}

	public boolean isErrorLecturaTipoIncorrecto() {
		return errorLecturaTipoIncorrecto;
	}

	public void setErrorLecturaTipoIncorrecto(boolean errorLecturaTipoIncorrecto) {
		this.errorLecturaTipoIncorrecto = errorLecturaTipoIncorrecto;
	}

	public String getMensajeError() {
		return mensajeError;
	}

	public void setMensajeError(String mensajeError) {
		this.mensajeError = mensajeError;
	}

	public String getResultado() {
		return resultado;
	}

	public void setResultado(String resultado) {
		this.resultado = resultado;
	}
        
	/* Comprueba que el tipo float es valido */
	public boolean comprobarFloat(String valor) {
	
		 boolean aux=false; boolean punto=false; boolean exp=false; 
		 int i = 0;
		 /*Busqueda del punto*/
		 while(i<valor.length() && !punto){  
			 punto=valor.charAt(i)=='.'; 
			 i++; 
		 }
		 
		 /*Busca la e de la exponencial*/
		 int j = 0;
		 while(j<valor.length() && !exp){  
			 exp = valor.charAt(j)=='E' || valor.charAt(j)=='e'; 
			 j++; 
		 }
		 
		 if (!punto) {
			 if (!exp) return aux = comprobarInteger(valor);
			 else return aux= comprobarInteger(valor.substring(0,j-1)) && comprobarInteger(valor.substring(j,valor.length())); 
		 }
		 else { // tiene punto
			aux = comprobarInteger(valor.substring(0,i-1)); //Compruebo la parte antes del punto
			 
			if (!exp) {
				
				if (valor.charAt(valor.length()-1) == '.') return false;// tiene un punto y ningun numero detras		
				if (valor.charAt(valor.length()-1) == '0' && valor.charAt(valor.length()-2) == '0') return false;
			} else {
				if (!esDigito(valor.charAt(i))) return false;// tiene un punto y ningun numero detras
				if (valor.charAt(j-2) == '0' && valor.charAt(j-3) == '0')  return false;
				aux = aux && comprobarInteger(valor.substring(j,valor.length())); 
				return aux;
			}
		  }	 
		return aux;

	
	}
        
/* Si tiene 1 digito miro si es 0-9.
	 * Si no vemos si se puede pasar a integer.
	 * Si se puede y el primero es un 0 o un - devuelve false si no true */
	private boolean comprobarNatural(String valor){
		boolean aux = false;
	
		if(valor.length()!=1){
			try { 
				Integer.parseInt(valor); 
				if (valor.charAt(0)=='0' || valor.charAt(0)=='-' || valor.charAt(0) == '+' ) aux = false; 
				else aux = true;
			}
			catch(Exception e) { return false; } ;
		} else aux = Character.isDigit(valor.charAt(0));
		return aux;
	}
	
	/* Si tiene 1 digito miro si es 0-9.
	 * Si no vemos si se puede pasar a integer.
	 * Si  tiene + o - llamamos a comprobarNatural sin el + o -.
	 * Si no llamamos a comprobarNatural de todo el string
	 * */
	private boolean comprobarInteger(String valor){
	  boolean aux = false;
	  
	  if(valor.length()!=1){
			try { 
				Integer.parseInt(valor); 
				if (valor.charAt(0) == '+' || valor.charAt(0) == '-') aux = comprobarNatural(valor.substring(1, valor.length()));
				else aux = (comprobarNatural(valor)) ;
			}
			catch(Exception e) { return false; } ;
		} else aux = Character.isDigit(valor.charAt(0));
		return aux;
	}
        
	private boolean esDigito(int c) {
		return (c >= '0' && c <= '9');
	}

}


